#ifndef xpack_chrono_date
#define xpack_chrono_date
#pragma push_macro("xuser")
#undef  xuser
#define xuser mixc::chrono_date::inc
#include"chrono/private/lookup_table.hpp"
#include"chrono/day.hpp"
#include"define/classify.hpp"
#include"macro/xexport.hpp"
#include"macro/xstruct.hpp"
#include"memop/zeros.hpp"
#pragma pop_macro("xuser")

namespace mixc::chrono_date{
    constexpr u08 month_leap  [] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    constexpr u08 month_normal[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    inline uxx days_of_month(bool is_leap, uxx month) {
        return (is_leap ? month_leap : month_normal)[month - 1];
    }
}

namespace mixc::chrono_date::inc{
    enum{
        bits_of_day     = 5,
        bits_of_month   = 4,
        bits_of_year    = 23,
    };
}

namespace mixc::chrono_date::origin{
    enum class day_of_week_t{
        sunday,     // 星期天
        monday,     // 星期一
        tuesday,    // 星期二
        wednesday,  // 星期三
        thursday,   // 星期四
        friday,     // 星期五
        saturday,   // 星期六
    };

    /**
     * @brief 
     * 日期类模板
     * @tparam the_t datex<> 的最终类型
     * @tparam field_t 构成年月日字段的类型
     */
    template<class field_t = u32>
    xstruct(
        xtmpl(datex  , field_t),
        xproc(m_day  , inc::bits_of_day, field_t),
        xproc(m_month, inc::bits_of_month, field_t),
        xproc(m_year , inc::bits_of_year, field_t), // 按照字节序用于比较
        xwhos(inc::classify_t::chrono_date)
    )
    public:
        xprops()
            xpubget_pubset(day, field_t);
            xpubget_pubset(month, field_t);
            xpubget_pubset(year, field_t);

            xpubgetx(is_valid, bool){
                xr{
                    if (xthe->month == zero or 
                        xthe->month > uxx(12) or 
                        xthe->day == zero or 
                        xthe->year == zero
                    ){
                        return false;
                    }

                    auto max_day = days_of_month(xthe->is_leap, xthe->month);
                    return xthe->day <= max_day;
                }
            };

            xpubgetx(is_leap, bool){
                xr{ return xthe->year % 4 == 0 and (xthe->year % 100 != 0 or xthe->year % 400 == 0); }
            };

            xpubgetx(tomorrow, the_t){
                xr{
                    auto max_day = days_of_month(xthe->is_leap, xthe->month);

                    if (xthe->day + 1 <= max_day){
                        return the_t{ xthe->year, xthe->month, xthe->day + 1 };
                    }
                    if (xthe->month + 1 <= 12){
                        return the_t(xthe->year, xthe->month + 1, 1);
                    }
                    else{
                        return the_t(xthe->year + 1, 1, 1);
                    }
                }
            };

            xpubgetx(yesterday, the_t){
                xr{
                    if (xthe->day > uxx(1)){
                        return the_t{xthe->year, xthe->month, xthe->day - 1};
                    }
                    if (xthe->month > uxx(1)){
                        auto max_day = days_of_month(xthe->is_leap, xthe->month - 1);
                        return the_t{xthe->year, xthe->month - 1, field_t(max_day)};
                    }
                    else{
                        return the_t{xthe->year - 1, 12, 31};
                    }
                }
            };

            xpubgetx(day_of_week, day_of_week_t){
                xr{
                    return day_of_week_t(
                        (inc::day(xthe) + 1) % 7
                    );
                }
            };

            xpubgetx(day_of_year, uxx){
                xr{
                    auto days = xthe->is_leap ? inc::sum_leap : inc::sum_normal;
                    return days[xthe->month - 1] + xthe->day;
                }
            };

            xpubgetx(day_index_of_year, uxx){
                xr{ return xthe->day_of_year - 1; }
            };
        $

        xprops_operator()

    public:
        /**
         * @brief 
         * 通过 chrono/now.hpp 初始化当前日期
         * @tparam now_t chrono/now.hpp 中的 now 符合该约束
         */
        template<inc::is_nowxx now_t>
        constexpr datex(now_t):
            datex(now_t::date()){
        }

        template<inc::is_date date_t>
        constexpr datex(date_t const & value):
            datex(value->year, value->month, value->day){
        }

        constexpr datex(){
            inc::zeros(this, sizeof(the_t)); // 因为位域中有些 bit 没有用到，那么在初始化时可能是未定义值，这里把整个结构体清零，避免存在脏数据
        }

        /**
         * @brief
         * 通过年月日初始化该结构
         * @param year 当前年份
         * @param month 当前月份
         * @param day 几号
         */
        constexpr datex(field_t year, field_t month = 0, field_t day = 0) :
            datex(){
            m_day   = day;
            m_month = month;
            m_year  = year;
        }

        /**
         * @brief 
         * 比较两个日期
         * @param value 参与比较的日期
         * @return ixx 
         * 当前日期 == value 返回 0
         * 当前日期 >  value 返回正数（不局限于 1）
         * 当前日期 <  value 返回负数（不局限于 -1）
         */
        ixx compare(the_t const & value) const {
            uxx left    = m_year        << (inc::bits_of_day + inc::bits_of_month) | m_month        << inc::bits_of_day | m_day;
            uxx right   = value.m_year  << (inc::bits_of_day + inc::bits_of_month) | value.m_month  << inc::bits_of_day | value.m_day;
            return left == right ? 0 : left < right ? -1 : 1;
        }

        /**
         * @brief 
         * 两个日期相差多少天
         * @param self 日期甲
         * @param value 日期乙
         * @return inc::day 相差多少天（有符号数）
         */
        friend inc::day operator - (the_t const & self, the_t const & value) {
            return inc::day(self) - inc::day(value);
        }

        /**
         * @brief 
         * 当前日期 value 天前对应的日期
         * @param self 日期甲
         * @param value 几天前
         * @return the_t 
         * 返回一个新的对象，不改变当前对象的值
         */
        friend the_t operator - (the_t const & self, inc::day value) {
            inc::day a = self;
            inc::day b = a - value;
            return b;
        }

        /**
         * @brief 
         * 当前日期 value 天后对应的日期
         * @param self 日期甲
         * @param value 几天后
         * @return the_t 
         * 返回一个新的对象，不改变当前对象的值
         */
        friend the_t operator + (the_t const & self, inc::day value) {
            inc::day a = self;
            inc::day b = a + value;
            return b;
        }

        /**
         * @brief 
         * 当前日期 value 天前对应的日期
         * @param self 日期甲
         * @param value 几天前
         * @return the_t 
         * 改变当前对象的值后，返回与当前对象值一样的新对象
         */
        friend the_t operator -= (the_t & self, inc::day value){
            self = self - value;
            return self;
        }

        /**
         * @brief 
         * 当前日期 value 天后对应的日期
         * @param self 日期甲
         * @param value 几天前
         * @return the_t 
         * 改变当前对象的值后，返回与当前对象值一样的新对象
         */
        friend the_t operator += (the_t & self, inc::day value){
            self = self + value;
            return self;
        }

        #define xa_args_list       the_t const & left, the_t const & right
        #define xa_invoke          left.compare(right)
        #define xa_is_friend
        #include"macro/xgen.cmp.hpp"
    $

    using date = datex<>;
}

#endif

xexport_space(mixc::chrono_date::origin)
